"Where you learn another way to mix code and bindings at will" from Metaprogramming Ruby
這兩個方法乍看似乎蠻雷同的,光看方法名稱可猜想也許是差不多的方法,只是ㄧ個是給實例物件用的,另一個給類別用的。但要了解這兩個方法的運作,實際上是比我預想的要複雜許多。
BasicObject#instance_eval() 方法會將當下的物件 (self) 轉為接收者(receiver)也就是實例物件(instance object),在實例物件的作用域 (scope) 內,所有的『實例變數』和『私有方法』都可以被存取。
先來看看下面的程式碼:
obj.instance_eval() 可以讓你進入到 obj 的作用域內,從第10行可得知當下的物件(self)是 MyClass的實例物件,因此在第11行可以讀取到實體 @v 的值。
注意到行數5以後都沒有 Scope Gates 的關鍵字 (Class/Module/Def),因此你可以帶著 v=2 進入第17行的 block,再傳給 instance_eval()方法。
接著定義 my_method()方法在 block 裡:
當定義 my_method() 在 (do…end) block 裡並且傳進 instance_eval(),這個 my_method() 就會變成 obj 物件專屬的方法,也就是之前介紹過的 singleton method。
相較於 instance_eval() 方法, instance_exec() 方法更加靈活多了因為多了一個可以傳入引數給 block 的可能性。
也許你會疑惑在14行為什麼 @y 沒有顯示出來呢?原因是 instance_eval() 會將 self 設為 C 類別的實例物件 (instance object of C),如果你檢視物件的作用域,很明顯只有 @x 這個實例變數。因此 @y 會是 nil,才會顯示空白。
你可以選擇將原本的實例變數 @y 改設為區域變數 y,這樣的話 block 就看得到 y,@y 的輸出值就會是 2。
y = 2
C.new.instance_eval { "@x: #{@x}, @y: #{y}" }
D.new.twisted_method # => "@x: 1, @y: 2"
比較好的做法是呼叫 instance_eval() 方法,讓 @x 和 @y 合併在相同的作用域下,就可以傳 @y 進入 block 了。
@y = 2
C.new.instance_exec(@y) { |y| "@x: #{@x}, @y: #{y}" }
D.new.twisted_method # => "@x: 1, @y: 2"
Module#class_eval() 方法會將當下的物件 (self) 轉設定為類別 (class),當下的類別 (current class) 就會被打開,就與先前提到的 Open Class 相似,你可以在類別內新增或是複寫方法。
換句話說, instance_eval() 方法是只能對單一物件做改變,如果你要對每一個實例物件做綁定就可以使用 Module#class_eval() 方法。
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj2 = MyClass.new
obj.instance_eval do
# Getter
def v
@v
end
end
p obj.v # 1
p obj2.v # undefined method `v' for...(NoMethodError)
instance_eval() 只能綁定單一物件,但如果要每一個物件都做 getter 方法就太麻煩了!
試試看 class_eval() 方法
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj2 = MyClass.new
MyClass.class_eval do
# Getter
def v
@v
end
end
p obj.v # 1
p obj2.v # 1
這樣就其實同等於在 MyClass 類別裡新增一個實例方法
class MyClass
def initialize
@v = 1
end
# Getter
def v
@v
end
end
另外要注意的是以往使用 Open Class 技巧去存取類別(class),我們是需要用關鍵字 class + 常數名稱去重新打開類別,這代表同時也開啟的新的作用域(scope)跟失去了舊的 bindings。但是如果用 class_eval()方法來打開類別,因為有 Flat Scope 的特性,則是可以在 class_eval 的 block 使用外層的變數。